import fs from 'fs/promises'
import path from 'path'
import crypto from 'crypto'
import { createReadStream } from 'fs'
import yauzl from 'yauzl'
import { config } from '@/config/env'
import { ValidationError, AppError } from '@/middleware/errorHandler'

// 类型定义
export interface UploadedFile {
  filename: string
  originalName: string
  size: number
  mimetype: string
  path: string
  hash: string
}

// 导入标准的插件类型定义
export interface PluginManifest {
  name: string // 插件名（唯一标识符）
  displayName?: string // 中文名/显示名称
  version: string
  description?: string
  author?: string
  category: string // 插件分类
  icon?: string // 图标文件路径（相对于插件根目录，如 "icon.png"）
  engines: { host: string }
  dependencies?: Record<string, string>;
  permissions?: string[]
  configSchema?: any
  frontend?: any
  backend?: any
}

export interface PluginPackageInfo {
  manifest: PluginManifest
  files: string[]
  size: number
  hash: string
  hasIcon: boolean
  hasReadme: boolean
  hasChangelog: boolean
}

export class FileService {
  private uploadDir: string

  constructor() {
    this.uploadDir = config.storage.path
    this.ensureUploadDir()
  }

  // 确保上传目录存在
  private async ensureUploadDir(): Promise<void> {
    try {
      await fs.access(this.uploadDir)
    } catch {
      await fs.mkdir(this.uploadDir, { recursive: true })
    }
  }

  // 生成文件哈希
  async generateFileHash(filePath: string): Promise<string> {
    const hash = crypto.createHash('sha256')
    const stream = createReadStream(filePath)
    
    for await (const chunk of stream) {
      hash.update(chunk)
    }
    
    return hash.digest('hex')
  }

  // 验证文件类型
  validateFileType(mimetype: string, filename: string): boolean {
    const allowedMimeTypes = [
      'application/zip',
      'application/x-zip-compressed',
      'application/octet-stream'
    ]
    
    const allowedExtensions = ['.zip']
    const ext = path.extname(filename).toLowerCase()
    
    return allowedMimeTypes.includes(mimetype) || allowedExtensions.includes(ext)
  }

  // 验证文件大小
  validateFileSize(size: number): boolean {
    return size <= config.storage.maxFileSize
  }

  // 保存上传的文件
  async saveUploadedFile(
    fileBuffer: Buffer,
    originalName: string,
    mimetype: string
  ): Promise<UploadedFile> {
    // 验证文件类型
    if (!this.validateFileType(mimetype, originalName)) {
      throw new ValidationError('Invalid file type. Only ZIP files are allowed.')
    }

    // 验证文件大小
    if (!this.validateFileSize(fileBuffer.length)) {
      throw new ValidationError(`File too large. Maximum size is ${config.storage.maxFileSize} bytes.`)
    }

    // 生成唯一文件名
    const timestamp = Date.now()
    const randomSuffix = crypto.randomBytes(8).toString('hex')
    const ext = path.extname(originalName)
    const filename = `plugin-${timestamp}-${randomSuffix}${ext}`
    const filePath = path.join(this.uploadDir, filename)

    // 保存文件
    await fs.writeFile(filePath, fileBuffer)

    // 生成文件哈希
    const hash = await this.generateFileHash(filePath)

    return {
      filename,
      originalName,
      size: fileBuffer.length,
      mimetype,
      path: filePath,
      hash
    }
  }

  // 解析插件包
  async parsePluginPackage(filePath: string): Promise<PluginPackageInfo> {
    return new Promise((resolve, reject) => {
      yauzl.open(filePath, { lazyEntries: true }, (err, zipfile) => {
        if (err) {
          reject(new ValidationError('Invalid ZIP file'))
          return
        }

        if (!zipfile) {
          reject(new ValidationError('Failed to open ZIP file'))
          return
        }

        const files: string[] = []
        let manifest: PluginManifest | null = null
        let hasIcon = false
        let hasReadme = false
        let hasChangelog = false

        zipfile.readEntry()

        zipfile.on('entry', (entry) => {
          const fileName = entry.fileName

          // 跳过目录
          if (fileName.endsWith('/')) {
            zipfile.readEntry()
            return
          }

          files.push(fileName)

          // 检查特殊文件
          const baseName = path.basename(fileName).toLowerCase()
          if (baseName === 'plugin.json' || baseName === 'manifest.json') {
            // 读取manifest文件
            zipfile.openReadStream(entry, (err, readStream) => {
              if (err) {
                reject(new ValidationError('Failed to read manifest file'))
                return
              }

              if (!readStream) {
                reject(new ValidationError('Failed to open manifest stream'))
                return
              }

              const chunks: Buffer[] = []
              readStream.on('data', (chunk) => chunks.push(chunk))
              readStream.on('end', () => {
                try {
                  const content = Buffer.concat(chunks).toString('utf8')
                  manifest = JSON.parse(content)
                  zipfile.readEntry()
                } catch (parseErr) {
                  reject(new ValidationError('Invalid manifest JSON'))
                }
              })
              readStream.on('error', (_streamErr) => {
                reject(new ValidationError('Failed to read manifest content'))
              })
            })
          } else {
            // 检查其他特殊文件
            if (baseName.includes('icon.') || baseName.includes('logo.')) {
              hasIcon = true
            } else if (baseName === 'readme.md' || baseName === 'readme.txt') {
              hasReadme = true
            } else if (baseName === 'changelog.md' || baseName === 'changelog.txt') {
              hasChangelog = true
            }
            
            zipfile.readEntry()
          }
        })

        zipfile.on('end', async () => {
          if (!manifest) {
            reject(new ValidationError('Plugin manifest (plugin.json) not found'))
            return
          }

          // 验证manifest
          const validationError = this.validateManifest(manifest)
          if (validationError) {
            reject(validationError)
            return
          }

          // 获取文件大小和哈希
          const stats = await fs.stat(filePath)
          const hash = await this.generateFileHash(filePath)

          resolve({
            manifest,
            files,
            size: stats.size,
            hash,
            hasIcon,
            hasReadme,
            hasChangelog
          })
        })

        zipfile.on('error', (_zipErr) => {
          reject(new ValidationError('Failed to process ZIP file'))
        })
      })
    })
  }

  // 验证插件manifest
  private validateManifest(manifest: any): ValidationError | null {
    // 必需字段检查
    if (!manifest.name || typeof manifest.name !== 'string') {
      return new ValidationError('Plugin name is required and must be a string')
    }

    if (!manifest.version || typeof manifest.version !== 'string') {
      return new ValidationError('Plugin version is required and must be a string')
    }

    if (!manifest.engines || typeof manifest.engines !== 'object') {
      return new ValidationError('Plugin engines specification is required')
    }

    if (!manifest.engines.host || typeof manifest.engines.host !== 'string') {
      return new ValidationError('Plugin host engine version is required')
    }

    // 检查category字段（如果提供）
    if (manifest.category && typeof manifest.category !== 'string') {
      return new ValidationError('Plugin category must be a string')
    }

    // 名称格式检查 (应该是 @scope/name 或 name 格式)
    const namePattern = /^(@[a-z0-9-]+\/)?[a-z0-9-]+$/
    if (!namePattern.test(manifest.name)) {
      return new ValidationError('Invalid plugin name format. Use @scope/name or name format with lowercase letters, numbers, and hyphens only.')
    }

    // 版本格式检查 (语义化版本)
    const versionPattern = /^\d+\.\d+\.\d+(-[a-z0-9-]+(\.[a-z0-9-]+)*)?(\+[a-z0-9-]+(\.[a-z0-9-]+)*)?$/
    if (!versionPattern.test(manifest.version)) {
      return new ValidationError('Invalid version format. Use semantic versioning (e.g., 1.0.0)')
    }

    // 验证permissions数组（如果提供）
    if (manifest.permissions && !Array.isArray(manifest.permissions)) {
      return new ValidationError('Plugin permissions must be an array')
    }

    return null
  }

  // 删除文件
  async deleteFile(filePath: string): Promise<void> {
    try {
      await fs.unlink(filePath)
    } catch (error) {
      // 文件不存在时忽略错误
      if ((error as any).code !== 'ENOENT') {
        throw new AppError('Failed to delete file', 500)
      }
    }
  }

  // 获取文件信息
  async getFileInfo(filePath: string): Promise<{ size: number; hash: string }> {
    const stats = await fs.stat(filePath)
    const hash = await this.generateFileHash(filePath)
    
    return {
      size: stats.size,
      hash
    }
  }

  // 清理临时文件
  async cleanupTempFiles(olderThanHours: number = 24): Promise<void> {
    try {
      const files = await fs.readdir(this.uploadDir)
      const cutoffTime = Date.now() - (olderThanHours * 60 * 60 * 1000)

      for (const file of files) {
        const filePath = path.join(this.uploadDir, file)
        const stats = await fs.stat(filePath)
        
        if (stats.mtime.getTime() < cutoffTime) {
          await this.deleteFile(filePath)
        }
      }
    } catch (error) {
      console.error('Failed to cleanup temp files:', error)
    }
  }
}
